package cn.hongwei.service.impl;

import cn.hongwei.dao.IndexPopupMapper;
import cn.hongwei.pojo.IndexPopup;
import cn.hongwei.service.IndexPopupService;
import cn.hongwei.service.UsersService;
import cn.hongwei.utils.UUIDUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Great
 * @email 1224028994@qq.com
 */
@Service
@Transactional
public class IndexPopupServiceImpl extends ServiceImpl<IndexPopupMapper, IndexPopup> implements IndexPopupService {

    //未跟用户绑定的弹窗
    private final static String DB_PREFIX = "default:db:";
    //跟用户绑定的弹窗
    private final static String USER_PREFIX = "default:usersNo:";
    //判断用户访问首页的标记
    private final static String USER_COUNT_PREFIX="count:";

    //跟弹窗实时性相关的标记
    private final static String POPUP_REAL_PREFIX="real:";

    @Autowired
    private UsersService usersService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void saveIndexPopup(IndexPopup indexPopup) {
        /*
         *添加一个首页弹窗，就要同步到redis中
         * 用string数据结构存储 key是弹窗的编号，value是indexPopup的json字符串
         * 用阿里的fastJson转换
         * */
        //赋值默认值
        indexPopup.setPopupNo(UUIDUtil.popupNo());
        indexPopup.setStatus(1);
        baseMapper.insert(indexPopup);
        //同步到redis
        redisTemplate.opsForValue().set(DB_PREFIX + indexPopup.getPopupNo(), JSONObject.toJSONString(indexPopup));
        //设置过期时间
        long time=indexPopup.getExpireTime().getTime()-System.currentTimeMillis();
        redisTemplate.expire(DB_PREFIX+indexPopup.getPopupNo(),time/1000,TimeUnit.SECONDS);
    }

    @Override
    public void updateIndexPopup(IndexPopup indexPopup) {
        /*
         * 更新弹窗稍微麻烦
         * 首先更新数据库中的弹窗
         * 再更新redis中的弹窗，也就是未跟用户绑定的弹窗
         * 再看看这些弹窗跟哪些用户绑定了，再次更新跟用户绑定的弹窗
         * 跟用户绑定的弹窗用list结构，key是用户编号，value是弹窗
         * */
        baseMapper.updateById(indexPopup);
        //查询更新完毕的数据
        IndexPopup updatePop = baseMapper.selectById(indexPopup.getId());
        //更新未跟用户绑定的弹窗
        redisTemplate.opsForValue().set(DB_PREFIX + indexPopup.getPopupNo(), JSONObject.toJSONString(updatePop));
        //-----------------------------//
        //接下来更新跟用户绑定的弹窗
        //查询所有用户的编号
        List<String> usersNos = usersService.queryAllUsersNo();
        //保存更新弹窗后的集合
        ArrayList<IndexPopup> updatePops = new ArrayList<>();
        for (String usersNo : usersNos) {
            //根据编号查询redis中跟用户绑定的弹窗
            List<String> usersPopups = redisTemplate.opsForList().range(USER_PREFIX + usersNo, 0, -1);
            for (String usersPopup : usersPopups) {
                IndexPopup popup = JSONObject.parseObject(usersPopup, IndexPopup.class);
                //通过弹窗的编号判断哪个弹窗需要更新
                if (updatePop.getPopupNo().equals(popup.getPopupNo())) {
                    updatePops.add(popup);
                } else {
                    updatePops.add(popup);
                }
            }
            /*
             * 内层循环结束以后，表示已经重新生成了更新完毕以后跟用户绑定的弹窗
             * 这里由于我不知道redis中list结构指定某一个元素更新，我就先删除，再更新
             * 无奈。。。
             * */
            redisTemplate.delete(USER_PREFIX + usersNo);
            for (IndexPopup pop : updatePops) {
                redisTemplate.opsForList().rightPush(USER_PREFIX + usersNo, JSONObject.toJSONString(pop));
            }
            //清空更新后的集合，以免影响其他用户的更新
            updatePops.clear();
            //设置过期时间
            long time = (DateUtil.endOfDay(new Date()).getTime()) - System.currentTimeMillis();
            redisTemplate.expire(USER_PREFIX+usersNo,time/1000,TimeUnit.SECONDS);
        }
    }


    @Override
    public void deleteById(Long id) {
        /**
         * 逻辑删除MySQL弹窗
         * 再删除未跟用户绑定的弹窗
         * 再删除跟用户绑定的弹窗
         */
        //把要删除的先查询出来给后续redis中删除使用
        IndexPopup queryById = baseMapper.selectById(id);
        //查询redis中所有的弹窗编号，供后续操作redis使用
        List<String> popupNos = queryAllPopupNoWithoutUsersInRedis();
        //删除MySQL
        baseMapper.deleteById(id);

        //删除未跟用户绑定的弹窗
        for (String popupNo : popupNos) {
            if (queryById.getPopupNo().equals(popupNo)) {
                redisTemplate.delete(DB_PREFIX + popupNo);
            }
        }

        /**
         * 删除跟用户绑定的弹窗
         *  1.获取所有用户的弹窗
         *      1.1.获取所有用户的编号，根据用户编号查找所有的弹窗
         *  2.通过用户绑定弹窗的编号来判断删除
         */
        List<String> usersNos = usersService.queryAllUsersNo();
        a:
        for (String usersNo : usersNos) {
            List<String> popups = redisTemplate.opsForList().range(USER_PREFIX + usersNo, 0, -1);
            for (String popup : popups) {
                //json->obj
                IndexPopup indexPopup = JSONObject.parseObject(popup, IndexPopup.class);
                if (queryById.getPopupNo().equals(indexPopup.getPopupNo())) {
                    redisTemplate.delete(USER_PREFIX + indexPopup.getPopupNo());
                    //进行下一轮用户的删除，因为一个用户只要删除一次，这里不是批量删除
                    continue a;
                }
            }
        }
    }

    @Override
    public List<IndexPopup> showAllPopups(String userNo) {
        //查询跟用户绑定的所有弹窗
        List<IndexPopup> indexPopups = queryAllPopupWithUserNoInRedis(userNo);
        //查询实时性的标记
        List<String> verifyPop = redisTemplate.opsForList().range(POPUP_REAL_PREFIX + userNo, 0, -1);
        if (!CollectionUtils.isEmpty(indexPopups)||!CollectionUtils.isEmpty(verifyPop)) {
            List<IndexPopup> rtIndexPopups = new ArrayList<>(indexPopups);
            //查询未跟用户绑定的弹窗
            List<IndexPopup> realPopupsWithoutUserInRedis = queryAllPopupsWithoutUserInRedis();
            List<IndexPopup> distinctPopups=null;
            if(!CollectionUtils.isEmpty(verifyPop)){
                List<IndexPopup> verifyPopups=new ArrayList<>();
                for (String s : verifyPop) {
                    verifyPopups.add(JSONObject.parseObject(s,IndexPopup.class));
                }

                //对上面两个集合去重
                distinctPopups = realPopupsWithoutUserInRedis.stream().filter(item -> {
                    return !verifyPopups.contains(item);
                }).collect(Collectors.toList());
            }

            /*
             * 有值说明之前访问过首页
             * 判断次数是否已经失效，失效的弹窗进行解绑（删除）
             * 判断哪些已经到期了的弹窗，对这些弹窗要进行解绑（删除）
             * 如果过了凌晨12点，那就重置所有的弹窗次数.这个逻辑放在第一次访问首页执行
             * */
            for (IndexPopup indexPopup : indexPopups) {
                if (indexPopup.getShowCount() == 0) {
                    //次数失效了
                    rtIndexPopups.remove(indexPopup);
                    redisTemplate.opsForList().remove(USER_PREFIX + userNo, 0, JSONObject.toJSONString(indexPopup));
                }
                if (indexPopup.getExpireTime().getTime() - System.currentTimeMillis() <= 0) {
                    //到期了
                    rtIndexPopups.remove(indexPopup);
                    redisTemplate.opsForList().remove(USER_PREFIX + userNo, 0, JSONObject.toJSONString(indexPopup));
                }
            }
            //对展示次数减一
            for (IndexPopup rtIndexPopup : rtIndexPopups) {
                rtIndexPopup.setShowCount(rtIndexPopup.getShowCount() - 1);
            }
            //把新增(去重后的)的弹窗添加到rtIndexPopups集合中
            if(distinctPopups!=null){
                for (IndexPopup distinctPopup : distinctPopups) {
                    distinctPopup.setShowCount(distinctPopup.getShowCount()-1);
                    rtIndexPopups.add(distinctPopup);
                }
                //将标记回写到redis
                awareWriteRedisFlagUser(userNo,realPopupsWithoutUserInRedis);
            }
            //回写到redis
            awareWriteRedisBoundUser(userNo, rtIndexPopups);

            return rtIndexPopups;
        }

        /*
         * 第一次访问：
         *   查询未跟用户绑定的所有弹窗
         *   将到期时间到了的删除，同样也删除MySQL中的
         *   剩下的有效弹窗设置一个到凌晨12点的过期时间
         *   对弹窗次数减一
         * */
        //判断访问标记，即今天全部弹窗完毕
        if(redisTemplate.opsForValue().get(USER_COUNT_PREFIX+userNo)!=null){
            return null;
        }
        //查询未跟用户绑定的弹窗
        List<IndexPopup> popupsInRedis = queryAllPopupsWithoutUserInRedis();
        List<IndexPopup> rtPopups = new ArrayList<>();
        for (IndexPopup popups : popupsInRedis) {
            if (popups.getExpireTime().getTime() - System.currentTimeMillis() <= 0) {
                redisTemplate.delete(DB_PREFIX + popups.getPopupNo());
                baseMapper.deleteById(popups.getId());
            } else {
                popups.setShowCount(popups.getShowCount() - 1);
                rtPopups.add(popups);
            }
        }
        //跟用户绑定存储到redis中
        for (IndexPopup rtPopup : rtPopups) {
            redisTemplate.opsForList().rightPush(USER_PREFIX + userNo, JSONObject.toJSONString(rtPopup));
            /*
             * 往redis存入一个标记，
             * 这个标记跟用户弹窗的实时性相关
             * */
            redisTemplate.opsForList().rightPush(POPUP_REAL_PREFIX+userNo,JSONObject.toJSONString(rtPopup));
        }

        long time = (DateUtil.endOfDay(new Date()).getTime()) - System.currentTimeMillis();
        //在redis中存入一个标记，用来判断用户今天的弹窗是否全部弹窗完毕，并且设置凌晨12点过期
        redisTemplate.opsForValue().set(USER_COUNT_PREFIX+userNo,"1");
        redisTemplate.expire(USER_COUNT_PREFIX + userNo, time / 1000, TimeUnit.SECONDS);
        redisTemplate.expire(USER_PREFIX + userNo, time / 1000, TimeUnit.SECONDS);
        redisTemplate.expire(POPUP_REAL_PREFIX+userNo,time / 1000, TimeUnit.SECONDS);
        return rtPopups;
    }

    //回写实时性标记弹窗
    private void awareWriteRedisFlagUser(String userNo,List<IndexPopup> indexPopups){
        redisTemplate.delete(POPUP_REAL_PREFIX+userNo);
        for (IndexPopup indexPopup : indexPopups) {
            redisTemplate.opsForList().rightPush(POPUP_REAL_PREFIX+userNo,JSONObject.toJSONString(indexPopup));
        }
        long time = (DateUtil.endOfDay(new Date()).getTime()) - System.currentTimeMillis();
        redisTemplate.expire(POPUP_REAL_PREFIX,time/1000,TimeUnit.SECONDS);
    }

    //把跟用户绑定的弹窗更新后回写到redis，也要设置过期时间
    private void awareWriteRedisBoundUser(String userNo, List<IndexPopup> rtIndexPopups) {
        //把更新好的绑定用户编号的数据回写到redis中
        //先删除，再回写
        redisTemplate.delete(USER_PREFIX + userNo);
        for (IndexPopup rtIndexPopup : rtIndexPopups) {
            redisTemplate.opsForList().rightPush(USER_PREFIX + userNo, JSONObject.toJSONString(rtIndexPopup));
        }
        long time = (DateUtil.endOfDay(new Date()).getTime()) - System.currentTimeMillis();
        redisTemplate.expire(USER_PREFIX+userNo,time/1000,TimeUnit.SECONDS);
    }

    /**
     * 查询redis中未跟用户绑定的所有弹窗
     * @return
     */
    private List<IndexPopup> queryAllPopupsWithoutUserInRedis() {
        //获取所有的弹窗编号
        List<String> popupNos = queryAllPopupNo();
        List<IndexPopup> rtPopups = new ArrayList<>();
        for (String popupNo : popupNos) {
            String jsonPopup = redisTemplate.opsForValue().get(DB_PREFIX + popupNo);
            rtPopups.add(JSONObject.parseObject(jsonPopup, IndexPopup.class));
        }
        return rtPopups;
    }

    //查询跟用户绑定的所有弹窗
    private List<IndexPopup> queryAllPopupWithUserNoInRedis(String userNo) {
        List<String> popupStrings = redisTemplate.opsForList().range(USER_PREFIX + userNo, 0, -1);
        List<IndexPopup> rtPopups = new ArrayList<>();
        for (String popupString : popupStrings) {
            IndexPopup indexPopup = JSONObject.parseObject(popupString, IndexPopup.class);
            rtPopups.add(indexPopup);
        }
        return rtPopups;
    }

    //查询redis中所有未跟用户绑定的弹窗编号
    private List<String> queryAllPopupNoWithoutUsersInRedis() {
        //首先查询MySQL中所有的弹窗编号,这个方法要在删除MySQL弹窗之前进行
        List<String> popNos = queryAllPopupNo();
        ArrayList<String> rtPopNos = new ArrayList<>();
        for (String popNo : popNos) {
            String jsonPopup = redisTemplate.opsForValue().get(DB_PREFIX + popNo);
            IndexPopup indexPopup = JSONObject.parseObject(jsonPopup, IndexPopup.class);
            rtPopNos.add(indexPopup.getPopupNo());
        }

        return rtPopNos;
    }

    //查询所有的弹窗编号
    private List<String> queryAllPopupNo() {
        List<String> popupNos = new ArrayList<>();
        QueryWrapper<IndexPopup> wrapper = new QueryWrapper();
        wrapper.select("popup_no");
        wrapper.eq("status", 1);
        List<IndexPopup> indexPopups = baseMapper.selectList(wrapper);
        for (IndexPopup indexPopup : indexPopups) {
            popupNos.add(indexPopup.getPopupNo());
        }
        return popupNos;
    }
}
